
#include <stdio.h>
#include <stdlib.h>
#include "acquire_client.h"

// For the command line options
#include <apr_general.h>
#include <apr_getopt.h>
#include <apr_strings.h>
#include <apr_signal.h>
#include "iface/APRInterface.h"
#include "utils/StringUtils.h"

// For the password prompt
#include "utils/Prompt.h"
#include "acquire_config.h"

#include <string>

typedef struct client_info
{
   char *brokername;
   char *hostname;
   char *hostport;
   char *username;
   char *ssh_machine;
   char *ssh_username;
   char *ssh_password;
   char *directorypath;
   char *subjectactor;
   char *handleactor;
   int do_register;
   int do_sshadd;
   int do_create_data;
   int do_delete_data;
   int do_upload_data;
   int do_download_data;
   int do_dump_tree;
} ClientArgs;

void processArguments(int argc, const char *argv[], ClientArgs &args)
{
   static const apr_getopt_option_t opt_option[] = {
      { "broker", 'b', TRUE, "internal name of the broker to connect to (broker)" },
      { "host", 'n', TRUE, "hostname to connect to (broker host)" },
      { "port", 'p', TRUE, "port to connect to (broker port)" },
      { "user", 'u', TRUE, "username" },
      { "directory", 'd', TRUE, "directory to use" },
      { "subjectactor", 's', TRUE, "actor to use" },
      { "resolveactor", 'r', TRUE, "actor handle to resolve" },
      { "addssh", 'A', FALSE, "add SSH host" },
      { "createdata", 'C', FALSE, "create data store" },
      { "uploaddata", 'U', FALSE, "upload data store" },
      { "downloaddata", 'D', FALSE, "download data store" },
      { "findchildren", 'F', FALSE, "find children of actor" },
      { "dumptree", 'T', FALSE, "dump out the tree of actors" },
      { NULL, 0, 0, NULL } };
   apr_getopt_t *opt;
   int optch;
   const char *optarg;
   apr_status_t status;
   apr_pool_t *mp = NULL;
   status = apr_pool_create(&mp, NULL);
   CheckAPRError(status);
   args.brokername = apr_pstrdup(mp, "broker");
   args.hostname = apr_pstrdup(mp, DEFAULT_HOST);
   args.hostport = apr_pstrdup(mp, DEFAULT_PORT_STRING);
   args.username = apr_pstrdup(mp, "");
   args.subjectactor = apr_pstrdup(mp, "");
   args.handleactor = apr_pstrdup(mp, "");
   args.directorypath = NULL;
   args.do_register = 0;
   args.do_sshadd = 0;
   args.do_create_data = 0;
   args.do_upload_data = 0;
   args.do_download_data = 0;
   args.do_dump_tree = 0;
   apr_getopt_init(&opt, mp, argc, argv);
   while ((status = apr_getopt_long(opt, opt_option, &optch, &optarg)) == APR_SUCCESS)
   {
      switch (optch)
      {
         case 'b': args.brokername = apr_pstrdup(mp, optarg); break;
         case 'n': args.hostname = apr_pstrdup(mp, optarg); break;
         case 'p': args.hostport = apr_pstrdup(mp, optarg); break;
         case 'u': args.username = apr_pstrdup(mp, optarg); break;
         case 'd': args.directorypath = apr_pstrdup(mp, optarg); break;
         case 's': args.subjectactor = apr_pstrdup(mp, optarg); break;
         case 'r': args.handleactor = apr_pstrdup(mp, optarg); break;
         case 'R': args.do_register = 1; break;
         case 'A': args.do_sshadd = 1; break;
         case 'C': args.do_create_data = 1; break;
         case 'U': args.do_upload_data = 1; break;
         case 'D': args.do_download_data = 1; break;
         case 'T': args.do_dump_tree = 1; break;
         default:
         {
            const apr_getopt_option_t *options = opt_option;
            int count = 0;
            printf("Help for client program:\n");
            printf("   progname");
            while (options[count].optch != '\0')
            {
               printf(" -%c", options[count].optch);
               if (options[count].has_arg)
                  printf(" <%s>", options[count].name);
               count++;
            }
            printf("\n\n");
            count = 0;
            while (options[count].optch != '\0')
            {
               printf("(-%c or --%s) - %s\n",
                  options[count].optch, options[count].name, options[count].description);
               count++;
            }
            return;
            break;
         }
      }
   }
   if (status != APR_EOF)
      CheckAPRError(status);
   // Never delete mp!
}

int main(int argc, const char *argv[])
{
   putARGV0ToGlobalPlace(argv[0]);
   int nargc = argc;
   const char *const *nargv = argv;
   acquireCommonInitLibraries();
   apr_signal_block(SIGPIPE);
   ClientArgs args;
   processArguments(argc, argv, args);
   apr_pool_t *mp = NULL;
   apr_status_t status = apr_pool_create(&mp, NULL);
   CheckAPRError(status);
//   Prompt own_password(NULL);
   
#ifdef USE_BUILTIN_INSECURE_PASSWORDS
   Prompt own_password("hello");
#else
   Prompt own_password(NULL);
#endif
   
   const char *mypassword = own_password.promptForPassword("User Password: ");
   if (!(acquireClientInitState(args.hostname, atoi(args.hostport), args.username, args.username, mypassword, args.brokername)))
   {
      printf("Error encountered while setting initial client state\n");
      return 1;
   }
   if (args.do_register)
   {
      printf("Registering login with Acquire broker...\n");
      int result = acquireClientRegisterUserWithBroker();
      switch (result)
      {
         case -1: printf("Unknown result\n"); break;
         case 0: printf("Missing information or access denied\n"); break;
         case ACQUIRE_USER_REGISTER__USER_IS_AUTHORISED: printf("User account is active\n"); break;
         case ACQUIRE_USER_REGISTER__IS_ACCEPTED_FOR_REVIEW: printf("This registration has been accepted subject to review by adminstrators\n"); break;
         case ACQUIRE_USER_REGISTER__NAME_IS_INVALID: printf("This username is invalid, or already in use\n"); break;
         case ACQUIRE_USER_REGISTER__USER_PENDING_REVIEW: printf("Registration is awaiting or under review by administrators\n"); break;
      }
   }
   if (args.do_sshadd)
   {
      printf("Adding SSH credentials to Acquire...\n");
      Prompt ssh_username(NULL);
      Prompt ssh_machine("127.0.0.1");
      Prompt ssh_pass(NULL);
      const char *my_ssh_machine = ssh_username.promptNormal("Hostname or IP address of machine to add to Acquire: ");
      const char *my_ssh_username = ssh_username.promptNormal("SSH username for machine: ");
      const char *my_ssh_password = ssh_username.promptForPassword("SSH password for machine: ");
      char *user_at_mach = StringUtils::varprintf("%s@%s", my_ssh_username, my_ssh_machine);
      int result = acquireClientAddSSHHost(user_at_mach, my_ssh_password, NULL, NULL);
      switch (result)
      {
         case ACQUIRE_USER_ADD_SSH__SSH_SUCCESS:
            printf("Successfully added this machine to Acquire.\n");
            break;
         case ACQUIRE_USER_ADD_SSH__FINGERPRINT_UNTRUSTED:
            printf("ACCESS DENIED: The fingerprint of this machine is untrusted by "
                   "the requested broker, and adding this machine could "
                   "be a security risk. Please contact your administrator.\n");
            break;
         case ACQUIRE_USER_ADD_SSH__SSH_ROUTING_FAILED:
            printf("ERROR: Could not reach the target machine.\n");
            break;
         case ACQUIRE_USER_ADD_SSH__SSH_ACCESS_DENIED:
            printf("ACCESS DENIED: Access denied. Please check your credentials were typed correctly.\n");
            break;
         default:
            printf("ERROR: Unknown error or failure occurred.\n");
            break;
      }
   }
   if (args.do_create_data)
   {
      printf("Creating data store...\n");
      int64_t remotespace = 0;
      int blocks = 0;
      int cancel = 0;
      const char *subjectactorname = acquireCommonCreateDataStore(args.directorypath, NULL, NULL, 1, 1048576, &remotespace, &blocks, &cancel);
      if (subjectactorname)
      {
         printf("Successfully created data store.\n");
      } else
      {
         printf("ERROR: Could not create data store\n");
      }
   }
   if (args.do_upload_data)
   {
      printf("Uploading '%s' to data store...\n", args.directorypath);
      int blocks = 0;
      int cancel = 0;
      // I'm assuming that the unlock password is this one in the state!
      if (strlen(args.subjectactor) == 0)
         args.subjectactor = apr_pstrdup(mp, acquireCommonResolve(args.handleactor));
      if (args.subjectactor == NULL)
      {
         printf("Given datastore empty, inaccessible or not found.\n");
         return 1;
      }
      char *input_keydata = StringUtils::varprintf("%s_inp", args.subjectactor);
      int retcode = acquireCommonUploadToDataStore(args.subjectactor, args.directorypath, 1048576,
         &blocks, &cancel, input_keydata);
      if (retcode == 0)
      {
         printf("Successfully uploaded data to datastore.\n");
         int retcode2 = acquireCommonSetPurposeFlagsOnDataStore(args.subjectactor, 1, 0, NULL, 0, 1, NULL);
         if (retcode2 == 1)
         {
            printf("Successfully set datastore purpose. Acquire should now start the work.\n");
         } else if (retcode2 == -1)
         {
            printf("ERROR: The broker could not start the work (do you have clusters available?)\n");
         } else
         {
            printf("ERROR: Failed to set the datastore purpose.\n");
         }
      } else
      {
         printf("ERROR: Did not set the data purpose on the broken data upload.\n");
      }
   }
   if (args.do_download_data)
   {
      int cancel = 0;
      acquireCommonDataStoreInfo dsinfo;
      if (strlen(args.subjectactor) == 0)
      {
         printf("Resolving datastore...\n");
         args.subjectactor = apr_pstrdup(mp, acquireCommonResolve(args.handleactor));
      }
      if (args.subjectactor == NULL)
      {
         printf("Datastore empty, inaccessible or not found");
         return 1;
      }
      printf("Getting data store metadata...\n");
      dsinfo = acquireCommonGetDataStoreInformation(args.subjectactor);
      if (dsinfo.isvalid)
      {
         printf("Got information about datastore to download from.\n");
      } else
      {
         printf("ERROR: Information about datastore corrupt, invalid or incomplete.\n");
      }
      printf("Downloading data...\n");
      int retcode1 = acquireCommonDeferredDownloadFromDataStore(args.subjectactor, "/tmp/", dsinfo.blockcount, &cancel);
      if (retcode1)
      {
         printf("ERROR: Failed to download data from this datastore.\n");
      } else
      {
         printf("Download from datastore successful.\n");
      }
      int retcode2 = acquireCommonReconstructDeferredDownload(args.subjectactor, "/tmp/", args.directorypath, dsinfo.blockcount, dsinfo.decryptionkey);
      if (retcode2)
      {
         printf("ERROR: Could not reconstruct files.\n");
      } else
      {
         printf("Reconstructed files from downloaded data.\n");
      }
   }
   if (args.do_dump_tree)
   {
      std::vector<const char *> stack;
      printf("Resolving tree of active items...\n");
      const char *res_username = acquireCommonResolve(args.username);
      if (res_username == NULL)
      {
         printf("Bad or non-existant username\n");
         return 1;
      }
      stack.push_back(apr_pstrdup(mp, res_username));
      while (stack.size() > 0)
      {
         const char *g_item = stack.at(stack.size() - 1);
         stack.pop_back();
         std::string resname_req(std::string(g_item) + std::string(".childrennames"));
         const char *resnames = acquireCommonResolve(resname_req.c_str());
         if (resnames != NULL)
         {
            std::vector<char *> *resultnames = StringUtils::unflattenStringList(resnames, ",");
            for (int i = 0; i < resultnames->size(); i++)
            {
               std::string hierstr(std::string(g_item) + std::string(".") + std::string(resultnames->at(i)));
               // do not free these!
               stack.push_back(apr_pstrdup(mp, hierstr.c_str()));
            }
         }
         std::string titem_progress_req(std::string(g_item) + std::string(".progress"));
         const char *titem_progress = acquireCommonResolve(titem_progress_req.c_str());
         std::string titem_id_req(std::string(g_item) + std::string(".id"));
         const char *titem_id = acquireCommonResolve(titem_id_req.c_str());
         std::string titem_type_req(std::string(g_item) + std::string(".type"));
         const char *titem_type = acquireCommonResolve(titem_type_req.c_str());
         std::string titem_name_req(std::string(g_item) + std::string(".localname"));
         const char *titem_localname = acquireCommonResolve(titem_name_req.c_str());
         if (titem_progress == NULL)
         {
            titem_progress = "";
         } else if (strcmp(titem_progress, "-1.0") == 0)
         {
            titem_progress = "(progress error)";
         } else
         {
            char progress_level[64];
            progress_level[0] = '\0';
            double ourprogress = atof(titem_progress);
            if (strcmp(titem_type, "datastores") == 0)
            {
               if (ourprogress == 0.)
                  sprintf(progress_level, "waiting for client upload");
               else if (ourprogress == 1.)
                  sprintf(progress_level, "upload complete");
               else
                  sprintf(progress_level, "uploading at %01d.%01d%% complete", (int)((ourprogress * 1000.) / 10.), ((int)(ourprogress * 1000.)) % 10);
            }
            if (strcmp(titem_type, "workinstances") == 0)
            {
               if (ourprogress == 0.)
                  sprintf(progress_level, "not running");
               else if (ourprogress == 1.)
                  sprintf(progress_level, "finished");
               else
                  sprintf(progress_level, "running at %01d.%01d%% complete", (int)((ourprogress * 1000.) / 10.), ((int)(ourprogress * 1000.)) % 10);
            }
            if (strcmp(titem_type, "users") == 0)
               sprintf(progress_level, "username");
            titem_progress = apr_pstrdup(mp, progress_level);
         }
         int dotcount = 0;
         for (int j = 0; j < strlen(g_item); j++)
            if (g_item[j] == '.')
               dotcount++;
         char *spacestring = (char *)malloc(sizeof(char)*(1 + dotcount*3));
         for (int j = 0; j < dotcount*3; j++)
            spacestring[j] = ' ';
         spacestring[dotcount*3] = '\0';
         printf("%s%s \t - %s\n", spacestring, titem_localname, titem_progress);
         errprint("%s%s (%s) \t - %s", spacestring, titem_localname, titem_id, titem_progress);
         free(spacestring);
      }
   }
   return 0;
}
//-u test -n 127.0.0.1 -p 10000 -d ../zebed_workpacketA_test/ -s 1b2b4c6206992496f30d15c9c3b1e7ac7a22211320954f8208e22387e23be322 -U
